package {{package}}.server;

import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import java.time.Instant;
import java.time.LocalDate;
import java.util.logging.Logger;

import jakarta.enterprise.context.ApplicationScoped;
import jakarta.inject.Inject;
import jakarta.validation.Valid;
import jakarta.validation.constraints.NotNull;

import com.oracle.bmc.monitoring.Monitoring;
import com.oracle.bmc.monitoring.model.Datapoint;
import com.oracle.bmc.monitoring.model.MetricDataDetails;
import com.oracle.bmc.monitoring.model.PostMetricDataDetails;
import com.oracle.bmc.monitoring.requests.PostMetricDataRequest;

import com.oracle.bmc.loggingingestion.Logging;
import com.oracle.bmc.loggingingestion.model.LogEntry;
import com.oracle.bmc.loggingingestion.model.LogEntryBatch;
import com.oracle.bmc.loggingingestion.model.PutLogsDetails;
import com.oracle.bmc.loggingingestion.requests.PutLogsRequest;

import org.eclipse.microprofile.config.inject.ConfigProperty;
import org.eclipse.microprofile.metrics.MetricUnits;
import org.eclipse.microprofile.metrics.annotation.Counted;
import org.eclipse.microprofile.metrics.annotation.Timed;

import {{package}}.server.api.GreetService;
import {{package}}.server.model.GreetResponse;
import {{package}}.server.model.GreetUpdate;

/**
 * A simple JAX-RS resource to greet you. Examples:
 *
 * Get default greeting message:
 * curl -X GET http://localhost:8080/greet
 *
 * Get greeting message for Joe:
 * curl -X GET http://localhost:8080/greet/Joe
 *
 * Change greeting
 * curl -X PUT -H "Content-Type: application/json" -d '{"greeting" : "Howdy"}' http://localhost:8080/greet/greeting
 *
 * The message is returned as a GreetResource model object generated by OpenApi tool.
 */
@ApplicationScoped
public class GreetResource implements GreetService {
    private static final Logger LOGGER = Logger.getLogger(GreetResource.class.getName());

    // OCI SDK integration of monitoring and logging
    private Monitoring monitoringClient;
    private Logging loggingClient;

    // Retrieve configuration parameters
    private String compartmentId;
    private String namespace;
    private String loggingId;

    /**
     * The greeting message provider.
     */
    private final GreetingProvider greetingProvider;

    private String hostName;

    /**
     * Using constructor injection to get required configuration.
     * By default this gets the value from META-INF/microprofile-config
     *
     * @param greetingConfig the configured greeting message
     */
    @Inject
    public GreetResource(Monitoring monitoringClient, Logging loggingClient,
                        @ConfigProperty(name = "oci.monitoring.compartmentId") String compartmentId,
                        @ConfigProperty(name = "oci.monitoring.namespace") String namespace,
                        @ConfigProperty(name = "oci.logging.id") String loggingId,
                        GreetingProvider greetingConfig) {
        LOGGER.info("GreetResource is instantiated");
        this.monitoringClient = monitoringClient;
        this.loggingClient = loggingClient;
        this.compartmentId = compartmentId;
        this.namespace = namespace;
        this.loggingId = loggingId;
        this.greetingProvider = greetingConfig;
        try {
            this.hostName = InetAddress.getLocalHost().getHostName();
        } catch (UnknownHostException e) {
            this.hostName = "HelidonOCITestHost";
        }
    }

    /**
     * Return a worldly greeting message via GreetResponse.
     *
     * @return {@link GreetResponse}
     */
    @Counted(name = "getDefaultMessage", absolute = true)
    @Override
    public GreetResponse getDefaultMessage() {
        LOGGER.info("getDefaultMessage() is invoked");
        publishMetricAndLog("getDefaultMessage");
        return createGreetResponse("World");
    }

    /**
     * Return a greeting message using the name that was provided.
     *
     * @param name the name to greet
     * @return {@link GreetResponse}
     */
    @Override
    public GreetResponse getMessage(String name) {
        LOGGER.info("getMessage(" +  name + ") is invoked");
        publishMetricAndLog("getMessage");
        return createGreetResponse(name);
    }

    /**
     * Return a greeting message using the name that was provided.
     *
     * @param body the name to greet
     * @return {@link GreetResponse}
     */
    @Timed(name = "updateGreeting", absolute = true, unit = MetricUnits.MILLISECONDS)
    @Override
    public void updateGreeting(@Valid @NotNull GreetUpdate body) {
        LOGGER.info("updateGreeting(greeting: " +  body.getGreeting() + ") is invoked");
        greetingProvider.setMessage(body.getGreeting());
        publishMetricAndLog("updateGreeting");
    }

    /**
     * Creates GreetResponse POJO that will contain the response data and
     * will be processed using Jackson.
     *
     * @param who the name to greet
     * @return {@link GreetResponse}
     */
    private GreetResponse createGreetResponse(String who) {
        GreetResponse greetResponse = new GreetResponse();
        greetResponse.setMessage(String.format("%s %s!", greetingProvider.getMessage(), who));
        greetResponse.setDate(LocalDate.now());
        return greetResponse;
    }

    private void publishMetricAndLog(String methodName) {
        publishMetric(methodName);
        publishLog(methodName);
    }

    /**
     * Developers do not need to explicitly publish Helidon metrics (declared using annotations or registered with the
     * MetricRegistry explicitly). This method shows how user code could publish their application metrics directly to OCI \
     * metrics.
     */
    private void publishMetric(String name) {
        // See https://github.com/oracle/oci-java-sdk/blob/v2.14.1/bmc-examples/src/main/java/MonitoringMetricPostExample.java#L72-L121
        List<Datapoint> datapoints =
            List.of(Datapoint.builder()
                            .timestamp(Date.from(Instant.now()))
                            .value(1.0)
                            .count(1)
                            .build());
        Map<String, String> dimensions =
            Map.of("host", this.hostName);

        MetricDataDetails metricDataDetails = MetricDataDetails.builder()
            .compartmentId(this.compartmentId)
            .namespace(this.namespace)
            .name(name)
            .datapoints(datapoints)
            .dimensions(dimensions)
            .build();

        PostMetricDataDetails postMetricDataDetails = PostMetricDataDetails.builder()
            .metricData(List.of(metricDataDetails))
            .build();

        PostMetricDataRequest postMetricDataRequest = PostMetricDataRequest.builder()
            .postMetricDataDetails(postMetricDataDetails)
            .build();

        try {
            this.monitoringClient.setEndpoint(monitoringClient.getEndpoint().replaceFirst("telemetry\\.", "telemetry-ingestion."));
            this.monitoringClient.postMetricData(postMetricDataRequest);
            LOGGER.info("publishMetric successfully sends metric to OCI monitoring service");
        } catch (Exception e) {
            LOGGER.warning(String.format(
                    "Received error while executing MonitoringClient.postMetricData(: %s", e.getMessage()));
        }
    }

    /**
     * A method a user may use to publish logs.
     */
    private void publishLog(String name) {
        Date entryDateTime = Date.from(Instant.now());
        PutLogsDetails putLogsDetails = PutLogsDetails.builder()
                .specversion("1.0")
                .logEntryBatches(new ArrayList<>(Arrays.asList(LogEntryBatch.builder()
                        .entries(new ArrayList<>(Arrays.asList(LogEntry.builder()
                                .data(String.format("%s was invoked on %tc", name, entryDateTime))
                                .id(String.format("helidon-oci-%s", UUID.randomUUID().toString())).build())))
                        .source(String.format("helidon-oci-%s", name))
                        .type(name)
                        .subject(String.format("processing-%s", name))
                        .defaultlogentrytime(entryDateTime).build()))).build();

        PutLogsRequest putLogsRequest = PutLogsRequest.builder()
                .logId(this.loggingId)
                .putLogsDetails(putLogsDetails)
                .timestampOpcAgentProcessing(Date.from(Instant.now()))
                .build();

        /* Send request to the Client */
        try {
            this.loggingClient.putLogs(putLogsRequest);
            LOGGER.info("publishLog() successfully sends log to OCI logging service");
        } catch (Exception e) {
            LOGGER.warning(
                    String.format("Received error while executing LoggingClient.putLogs: %s", e.getMessage()));
        }
    }
}
